//===============================================================================
// Microsoft Architecture Strategy Team
// LitwareHR - SaaS Sample Application
//===============================================================================
// Copyright  Microsoft Corporation.  All rights reserved.
// THIS CODE AND INFORMATION IS PROVIDED "AS IS" WITHOUT WARRANTY
// OF ANY KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING BUT NOT
// LIMITED TO THE IMPLIED WARRANTIES OF MERCHANTABILITY AND
// FITNESS FOR A PARTICULAR PURPOSE.
//===============================================================================
// The example companies, organizations, products, domain names,
// e-mail addresses, logos, people, places, and events depicted
// herein are fictitious.  No association with any real company,
// organization, product, domain name, email address, logo, person,
// places, or events is intended or should be inferred.
//===============================================================================

using System;
using System.IdentityModel.Tokens;
using System.Security.Cryptography.X509Certificates;
using System.Security.Permissions;
using System.ServiceModel;
using System.Globalization;
using System.Collections.ObjectModel;
using System.IdentityModel.Claims;
using System.Collections.Generic;
using System.ServiceModel.Configuration;

namespace Shp.Security.BrokeredReceiver
{
	static class FederationUtilities
	{
        /// <summary>
        /// Throws the trust fault.
        /// </summary>
        /// <param name="message">The message.</param>
        static public void ThrowTrustFault(string message)
        {
            throw new FaultException<TrustFault>(
                new TrustFault(message),
                new FaultReason(new FaultReasonText(Properties.Resources.TrustFaultFaultReasonText, CultureInfo.CurrentCulture)));
        }

        /// <summary>
        /// Throws the trust fault.
        /// </summary>
        /// <param name="message">The message.</param>
        /// <param name="args">The args.</param>
        static public void ThrowTrustFault(string message, params object[] args)
        {
            throw new FaultException<TrustFault>(
                new TrustFault(string.Format(CultureInfo.CurrentCulture, message, args)),
                new FaultReason(new FaultReasonText(Properties.Resources.TrustFaultFaultReasonText, CultureInfo.CurrentCulture)));
        }

        /// <summary>
        /// Utility method to get a X509 Token from a given certificate
        /// </summary>
        /// <param name="storeName">Name of certificate store (e.g. My, TrustedPeople)</param>
        /// <param name="storeLocation">Location of certificate store (e.g. LocalMachine, CurrentUser)</param>
        /// <param name="subjectDistinguishedName">The Subject Distinguished Name of the certificate</param>
        /// <returns>The corresponding X509 Token</returns>
        static public X509SecurityToken GetX509TokenFromCert(StoreName storeName,
                                                             StoreLocation storeLocation,
                                                             string subjectDistinguishedName)
        {
            X509Certificate2 certificate = LookupCertificate(storeName, storeLocation, subjectDistinguishedName);
            X509SecurityToken t = new X509SecurityToken(certificate);
            return t;
        }

        /// <summary>
        /// Gets the certificates used for encrypting the SAML token.
        /// </summary>
        /// <value>The scoped certificates.</value>
        static public Dictionary<Uri, X509SecurityToken> GetScopedTokens(X509ScopedServiceCertificateElementCollection certificates)
        {
            Dictionary<Uri, X509SecurityToken> scopedCertificates = new Dictionary<Uri, X509SecurityToken>();
            foreach (X509ScopedServiceCertificateElement certificate in certificates)
            {
                scopedCertificates.Add(certificate.TargetUri,
                    GetX509TokenFromCert(certificate.StoreName, certificate.StoreLocation, certificate.FindValue)); 
            }
            return scopedCertificates;
        }

        /// <summary>
        /// Utility method to get an X509 Certificate thumbprint
        /// </summary>
        /// <param name="storeName">Name of certificate store (e.g. My, TrustedPeople)</param>
        /// <param name="storeLocation">Location of certificate store (e.g. LocalMachine, CurrentUser)</param>
        /// <param name="subjectDistinguishedName">The Subject Distinguished Name of the certificate</param>
        /// <returns>The corresponding X509 Certificate thumbprint</returns>
        static public byte[] GetCertificateThumbprint(StoreName storeName, StoreLocation storeLocation, string subjectDistinguishedName)
        {
            X509Certificate2 certificate = LookupCertificate(storeName, storeLocation, subjectDistinguishedName);
            return certificate.GetCertHash();
        }

        static public ClaimSet GetX509CertificateClaimSet(ReadOnlyCollection<ClaimSet> claimSets)
        {
            List<ClaimSet> claimsets = new List<ClaimSet>(claimSets);
            ClaimSet myClaimSet = claimsets.Find((Predicate<ClaimSet>)delegate(ClaimSet target)
            {
                X509CertificateClaimSet certClaimSet = target.Issuer as X509CertificateClaimSet;
                return certClaimSet != null &&
                    certClaimSet.X509Certificate.Subject == ServiceConfiguration.IssuerDistinguishedName;
            });
            return myClaimSet;
        }

        static public bool IsTrustedIssuer(ClaimSet issuerClaimSet)
        {
            if (issuerClaimSet == null)
            {
                return false;
            }

            IEnumerable<Claim> issuerClaims = issuerClaimSet.FindClaims(ClaimTypes.Thumbprint, null);

            if (issuerClaims == null)
            {
                return false;
            }

            IEnumerator<Claim> issuerClaimsEnum = issuerClaims.GetEnumerator();
            Claim issuerClaim = null;
            if (issuerClaimsEnum.MoveNext())
            {
                issuerClaim = issuerClaimsEnum.Current;
            }

            if (issuerClaim == null ||
                issuerClaim.ClaimType != ClaimTypes.Thumbprint ||
                issuerClaim.Resource == null)
            {
                return false;
            }

            byte[] issuerThumbprint = (byte[])issuerClaim.Resource;
            byte[] certThumbprint = FederationUtilities.GetCertificateThumbprint(ServiceConfiguration.IssuerStoreName,
                                                                     ServiceConfiguration.IssuerStoreLocation,
                                                                     ServiceConfiguration.IssuerDistinguishedName);
            if (issuerThumbprint.Length != certThumbprint.Length)
            {
                return false;
            }

            for (int i = 0; i < issuerThumbprint.Length; i++)
            {
                if (issuerThumbprint[i] != certThumbprint[i])
                {
                    return false;
                }
            }

            return true;
        }

        /// <summary>
        /// Utility method to get a certificate from a given store
        /// </summary>
        /// <param name="storeName">Name of certificate store (e.g. My, TrustedPeople)</param>
        /// <param name="storeLocation">Location of certificate store (e.g. LocalMachine, CurrentUser)</param>
        /// <param name="subjectDistinguishedName">The Subject Distinguished Name of the certificate</param>
        /// <returns>The specified X509 certificate</returns>
        private static X509Certificate2 LookupCertificate(StoreName storeName, StoreLocation storeLocation, string subjectDistinguishedName)
        {
            X509Store store = null;
            try
            {
                store = new X509Store(storeName, storeLocation);
                store.Open(OpenFlags.ReadOnly);
                X509Certificate2Collection certs = store.Certificates.Find(X509FindType.FindBySubjectDistinguishedName,
                                                                           subjectDistinguishedName, false);
                if (certs.Count != 1)
                {
                    throw new Exception(String.Format(Properties.Resources.CertificateNotFound, subjectDistinguishedName));
                }
                return (X509Certificate2)certs[0];
            }
            finally
            {
                if (store != null)
                {
                    store.Close();
                }
            }
        }
    }
}
